define(function (require, exports, module) {
    const {
        noTarget,
        isOfMetaType,
        isActiveFeature,
        isInactiveFeature,
        isShiftDown
    } = require('./utils/common_selectors.js').onload();
    const createSupplementaryPoints = require('./utils/create_supplementary_points').onload();
    const constrainFeatureMovement = require('./utils/constrain_feature_movement').onload();
    const doubleClickZoom = require('./utils/double_click_zoom').onload();
    const Constants = require('./utils/constants.js').onload();
    const { getPixelBetweenTowPointsWithZoom } = require('./utils/pixelUtils.js').onload();

    function onload() {

        const isVertex = isOfMetaType(Constants.meta.VERTEX);
        const isMidpoint = isOfMetaType(Constants.meta.MIDPOINT);

        const DirectSelect = {};
        // 最小像素允许, 低于这个像素视为同一位置
        const minPixelAllow = 20

        let others;

        // INTERNAL FUCNTIONS

        DirectSelect.fireUpdate = function () {
            this.map.fire(Constants.events.UPDATE, {
                action: Constants.updateActions.CHANGE_COORDINATES,
                features: this.getSelected().map(f => f.toGeoJSON())
            });
        };

        DirectSelect.fireActionable = function (state) {
            this.setActionableState({
                combineFeatures: false,
                uncombineFeatures: false,
                trash: state.selectedCoordPaths.length > 0
            });
        };

        DirectSelect.startDragging = function (state, e) {
            this.map.dragPan.disable();
            state.canDragMove = true;
            state.dragMoveLocation = e.lngLat;
        };

        DirectSelect.stopDragging = function (state) {
            this.map.dragPan.enable();
            state.dragMoving = false;
            state.canDragMove = false;
            state.dragMoveLocation = null;
        };

        DirectSelect.onVertex = function (state, e) {
            this.startDragging(state, e);
            const about = e.featureTarget.properties;
            const selectedIndex = state.selectedCoordPaths.indexOf(about.coord_path);
            if (!isShiftDown(e) && selectedIndex === -1) {
                state.selectedCoordPaths = [about.coord_path];
            } else if (isShiftDown(e) && selectedIndex === -1) {
                state.selectedCoordPaths.push(about.coord_path);
            }

            const selectedCoordinates = this.pathsToCoordinates(state.featureId, state.selectedCoordPaths);
            this.setSelectedCoordinates(selectedCoordinates);
        };

        DirectSelect.onMidpoint = function (state, e) {
            this.startDragging(state, e);
            const about = e.featureTarget.properties;
            state.feature.addCoordinate(about.coord_path, about.lng, about.lat);
            this.fireUpdate();
            state.selectedCoordPaths = [about.coord_path];
        };

        DirectSelect.pathsToCoordinates = function (featureId, paths) {
            return paths.map(coord_path => ({feature_id: featureId, coord_path}));
        };

        DirectSelect.onFeature = function (state, e) {
            if (state.selectedCoordPaths.length === 0) this.startDragging(state, e);
            else this.stopDragging(state);
        };

        DirectSelect.dragFeature = function (state, e, delta) {
            // moveFeatures(this.getSelected(), delta);
            // state.dragMoveLocation = e.lngLat;
        };

        DirectSelect.dragVertex = function (state, e, delta) {
            const selectedCoords = state.selectedCoordPaths.map(coord_path => state.feature.getCoordinate(coord_path));
            const selectedCoordPoints = selectedCoords.map(coords => ({
                type: Constants.geojsonTypes.FEATURE,
                properties: {},
                geometry: {
                    type: Constants.geojsonTypes.POINT,
                    coordinates: coords
                }
            }));

            const constrainedDelta = constrainFeatureMovement(selectedCoordPoints, delta);
            for (let i = 0; i < selectedCoords.length; i++) {
                const coord = selectedCoords[i];
                const currentCoordinate = [coord[0] + constrainedDelta.lng, coord[1] + constrainedDelta.lat]
                const coordinate = adjustCurrentCoordinate(currentCoordinate, this.map.getZoom())
                // state.feature.updateCoordinate(state.selectedCoordPaths[i], currentCoordinate[0], currentCoordinate[1]);
                state.feature.updateCoordinate(state.selectedCoordPaths[i], coordinate[0], coordinate[1]);
            }
        };

        /**
         * 矫正当前坐标: 当前点在地图上的任何一个已有多边形上, 则矫正为这个多边形上距离其最近的点
         * @param currentCoordinate 当前坐标
         * @param zoomLevel 层级
         * @returns {*}
         */
        const adjustCurrentCoordinate = (currentCoordinate, zoomLevel) => {
            if (others) {
                const stickToFeatures = others.features
                if (stickToFeatures.length !== 0) {
                    // 当前点
                    const currentPoint = turf.point(currentCoordinate)
                    for (let i = 0; i < stickToFeatures.length; i++) {
                        const otherFeature = stickToFeatures[i];
                        // 优先返回顶点
                        // 多面坐标
                        const multiPolygonCoordinates = otherFeature.geometry.coordinates;
                        for (let j = 0; j < multiPolygonCoordinates.length; j++) {
                            // 单面坐标
                            const polygonCoordinates = multiPolygonCoordinates[j]
                            for (let k = 0; k < polygonCoordinates.length; k++) {
                                // 外环或内环坐标
                                const coordinates = polygonCoordinates[k]
                                // 外环或内环
                                const ring = turf.lineString(coordinates);
                                for (let l = 0; l < coordinates.length - 1; l++) {
                                    // 点坐标
                                    const coordinate = coordinates[l]
                                    if (getPixelBetweenTowPointsWithZoom(currentCoordinate, coordinate, zoomLevel) < minPixelAllow) {
                                        return coordinate
                                    }
                                }

                                // 顶点不符合，则尝试返回外环或内环上距离最近的点
                                const nearestPoint = turf.nearestPointOnLine(ring, currentPoint);
                                const nearestPointCoordinates = nearestPoint.geometry.coordinates;
                                if (getPixelBetweenTowPointsWithZoom(currentCoordinate, nearestPointCoordinates, zoomLevel) < minPixelAllow) {
                                    return nearestPointCoordinates
                                }
                            }
                        }
                    }
                }
            }
            return currentCoordinate
        }

        DirectSelect.clickNoTarget = function () {
            this.changeMode(Constants.modes.SIMPLE_SELECT);
        };

        DirectSelect.clickInactive = function () {
            this.changeMode(Constants.modes.SIMPLE_SELECT);
        };

        DirectSelect.clickActiveFeature = function (state) {
            state.selectedCoordPaths = [];
            this.clearSelectedCoordinates();
            state.feature.changed();
        };

// EXTERNAL FUNCTIONS

        DirectSelect.onSetup = function (opts) {
            const othersStr = localStorage.getItem('grid-edit-others')
            if (othersStr) {
                others = JSON.parse(othersStr);
            }

            const featureId = opts.featureId;
            const feature = this.getFeature(featureId);

            if (!feature) {
                throw new Error('You must provide a featureId to enter direct_select mode');
            }

            if (feature.type === Constants.geojsonTypes.POINT) {
                throw new TypeError('direct_select mode doesn\'t handle point features');
            }

            const state = {
                featureId,
                feature,
                dragMoveLocation: opts.startPos || null,
                dragMoving: false,
                canDragMove: false,
                selectedCoordPaths: opts.coordPath ? [opts.coordPath] : []
            };

            this.setSelectedCoordinates(this.pathsToCoordinates(featureId, state.selectedCoordPaths));
            this.setSelected(featureId);
            doubleClickZoom.disable(this);

            this.setActionableState({
                trash: true
            });

            return state;
        };

        DirectSelect.onStop = function () {
            doubleClickZoom.enable(this);
            this.clearSelectedCoordinates();
        };

        DirectSelect.toDisplayFeatures = function (state, geojson, push) {
            if (state.featureId === geojson.properties.id) {
                geojson.properties.active = Constants.activeStates.ACTIVE;
                push(geojson);
                createSupplementaryPoints(geojson, {
                    map: this.map,
                    midpoints: true,
                    selectedPaths: state.selectedCoordPaths
                }).forEach(push);
            } else {
                geojson.properties.active = Constants.activeStates.INACTIVE;
                push(geojson);
            }
            this.fireActionable(state);
        };

        DirectSelect.onTrash = function (state) {
            // Uses number-aware sorting to make sure '9' < '10'. Comparison is reversed because we want them
            // in reverse order so that we can remove by index safely.
            state.selectedCoordPaths
                .sort((a, b) => b.localeCompare(a, 'en', {numeric: true}))
                .forEach(id => state.feature.removeCoordinate(id));
            this.fireUpdate();
            state.selectedCoordPaths = [];
            this.clearSelectedCoordinates();
            this.fireActionable(state);
            if (state.feature.isValid() === false) {
                this.deleteFeature([state.featureId]);
                this.changeMode(Constants.modes.SIMPLE_SELECT, {});
            }
        };

        DirectSelect.onMouseMove = function (state, e) {
            // On mousemove that is not a drag, stop vertex movement.
            const isFeature = isActiveFeature(e);
            const onVertex = isVertex(e);
            const noCoords = state.selectedCoordPaths.length === 0;
            if (isFeature && noCoords) this.updateUIClasses({mouse: Constants.cursors.MOVE});
            else if (onVertex && !noCoords) this.updateUIClasses({mouse: Constants.cursors.MOVE});
            else this.updateUIClasses({mouse: Constants.cursors.NONE});
            this.stopDragging(state);
        };

        DirectSelect.onMouseOut = function (state) {
            // As soon as you mouse leaves the canvas, update the feature
            if (state.dragMoving) this.fireUpdate();
        };

        DirectSelect.onTouchStart = DirectSelect.onMouseDown = function (state, e) {
            if (isVertex(e)) return this.onVertex(state, e);
            if (isActiveFeature(e)) return this.onFeature(state, e);
            if (isMidpoint(e)) return this.onMidpoint(state, e);
        };

        DirectSelect.onDrag = function (state, e) {
            if (state.canDragMove !== true) return;
            state.dragMoving = true;
            e.originalEvent.stopPropagation();

            const delta = {
                lng: e.lngLat.lng - state.dragMoveLocation.lng,
                lat: e.lngLat.lat - state.dragMoveLocation.lat
            };
            if (state.selectedCoordPaths.length > 0) {
                const coordinate = adjustCurrentCoordinate([e.lngLat.lng, e.lngLat.lat], this.map.getZoom())
                state.feature.updateCoordinate(state.selectedCoordPaths[0], coordinate[0], coordinate[1]);
                // this.dragVertex(state, e, delta);
            }
            // else this.dragFeature(state, e, delta);

            state.dragMoveLocation = e.lngLat;
        };

        DirectSelect.onClick = function (state, e) {
            if (noTarget(e)) return this.clickNoTarget(state, e);
            if (isActiveFeature(e)) return this.clickActiveFeature(state, e);
            if (isInactiveFeature(e)) return this.clickInactive(state, e);
            this.stopDragging(state);
        };

        DirectSelect.onTap = function (state, e) {
            if (noTarget(e)) return this.clickNoTarget(state, e);
            if (isActiveFeature(e)) return this.clickActiveFeature(state, e);
            if (isInactiveFeature(e)) return this.clickInactive(state, e);
        };

        DirectSelect.onTouchEnd = DirectSelect.onMouseUp = function (state) {
            if (state.dragMoving) {
                this.fireUpdate();
            }
            this.stopDragging(state);
        };

        return {
            DirectSelect
        }
    }

    module.exports = {
        onload
    }
});
